﻿//IMarketEmulator.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Testing
{
	using System;
	using System.Collections.Generic;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Настройки эмулятора биржи.
	/// </summary>
	public class MarketEmulatorSettings
	{
		/// <summary>
		/// Создать <see cref="MarketEmulatorSettings"/>.
		/// </summary>
		public MarketEmulatorSettings()
		{
			DepthExpirationTime = TimeSpan.FromDays(1);
			FillOnTouch = false;
			SyncDepthToTrades = true;
			MaxFlushTime = TimeSpan.FromMilliseconds(0);
			IsSupportAtomicReRegister = true;
			FillMode = FillModes.Keep;
		}

		private Unit _slippage = new Unit();

		/// <summary>
		/// Максимальный уровень проскальзывания, который может происходить при исполнении заявки.
		/// По умолчанию проскальзывание отсутствует.
		/// </summary>
		public Unit Slippage
		{
			get { return _slippage; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_slippage = value;
			}
		}

		/// <summary>
		/// При эмулировании сведения по сделкам, производить сведение заявок, когда цена сделки коснулась цены заявки (равна цене заявки),
		/// а не только, когда цена сделки лучше цены заявки. По-умолчанию выключено (пессимисичный случай).
		/// </summary>
		public bool FillOnTouch { get; set; }

		/// <summary>
		/// Режимы разрешения конфликтов.
		/// </summary>
		public enum FillModes
		{
			/// <summary>
			/// Заявка целиком сводится.
			/// </summary>
			Fill,

			/// <summary>
			/// Остаток заявки после первоначального сведения снимается.
			/// </summary>
			Cancel,

			/// <summary>
			/// Заявка остается, несмотря на то что конфликтует со стаканом.
			/// При поступлении последующих стаканов она будет возможно сведена.
			/// </summary>
			Keep
		}

		/// <summary>
		/// Стакан "наезжает" на эмулируемую заявку. В результате заявка и стакан конфликтуют.
		/// По умолчанию режим равен <see cref="FillModes.Fill"/>.
		/// </summary>
		public FillModes FillMode { get; set; }

		/// <summary>
		/// Максимальное время, которое стакан находится в эмуляторе. Если за это время не произошло обновление, стакан стирается.
		/// Это свойство можно использовать, чтобы убирать старые стаканы при наличии дыр в данных. По-умолчанию равно 1 дню.
		/// </summary>
		public TimeSpan DepthExpirationTime { get; set; }

		/// <summary>
		/// Синхронизировать стаканы со сделками в эмуляторе (из снэпшота стакана убираются уровни по которым прошли сделки).
		/// Если режим выключен, новые заявки имитируются на вновь поступивших стаканах (появляется задержка). По-умолчанию выключено.
		/// </summary>
		public bool SyncDepthToTrades { get; set; }

		/// <summary>
		/// Процентное значение ошибки регистрации новых заявок.
		/// Значение может быть от 0 (не будет ни одной ошибки) до 100. По-умолчанию значение равно 0.
		/// </summary>
		public int Failing { get; set; }

		/// <summary>
		/// Минимальное значение задержки выставляемых заявок.
		/// По-умолчанию равно <see cref="TimeSpan.Zero"/>, что означает мгновенное принятие биржей выставляемых заявок.
		/// </summary>
		public TimeSpan Latency { get; set; }

		/// <summary>
		/// Поддерживается ли перерегистрация заявок в виде одной транзакции. По-умолчанию включено.
		/// </summary>
		public bool IsSupportAtomicReRegister { get; set; }

		/// <summary>
		/// Использовать стаканы для эмуляции. По умолчанию стаканы не используются.
		/// </summary>
		public bool UseMarketDepth { get; set; }

		/// <summary>
		/// Производить эмуляцию используя ордер-лог. Входные стаканы и сделки будут игнорироваться эмулятором.
		/// При этом в исходящие сообщения эмулятора будут генерироваться сделки и стаканы по ордерлогу.
		/// По умолчанию выключено.
		/// </summary>
		public bool UseOrderLog { get; set; }

		/// <summary>
		/// При эмуляции на ордер-логе пытаться определять <see cref="Trade.Id"/> реальных сделок при эмуляции.
		/// </summary>
		public bool UseOrderLogTrades { get; set; } 

		/// <summary>
		/// Посылать стратегии сделки/ордерлог пакетам. Длина пакета РТС 75мс. Опция задает максимальное 
		/// время между cбросами пакетов эмулятором. Реальне время может быть записано.
		/// По умолчанию 0 мс.
		/// </summary>
		public TimeSpan MaxFlushTime { get; set; }
	}

	/// <summary>
	/// Настройки эмулятора на уровне инструмента.
	/// </summary>
	public class SecurityEmulationSettings
	{
		private readonly MarketEmulatorSettings _parent;

		internal SecurityEmulationSettings(MarketEmulatorSettings parent)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			_parent = parent;
		}

		private Unit _slippage;

		/// <summary>
		/// Максимальный уровень проскальзывания, который может происходить при исполнении заявки.
		/// По умолчанию проскальзывание отсутствует.
		/// </summary>
		public Unit Slippage
		{
			get { return _slippage ?? _parent.Slippage; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_slippage = value;
			}
		}

		private bool? _useOrderLog = false;

		/// <summary>
		/// Производить эмуляцию используя ордер-лог. Входные стаканы и сделки будут игнорироваться эмулятором.
		/// При этом в исходящие сообщения эмулятора будут генерироваться сделки и стаканы по ордерлогу.
		/// По умолчанию см. <see cref="MarketEmulatorSettings.UseOrderLog"/>.
		/// </summary>
		public bool UseOrderLog
		{
			get { return _useOrderLog ?? _parent.UseOrderLog; }
			set { _useOrderLog = value; }
		}
	}

	/// <summary>
	/// Интерфейс, описывающий эмулятор биржи.
	/// </summary>
	public interface IMarketEmulator : IDisposable
	{
		/// <summary>
		/// Настройки эмулятора биржи.
		/// </summary>
		MarketEmulatorSettings Settings { get; }

		/// <summary>
		/// Получить все заявки.
		/// </summary>
		IEnumerable<Order> Orders { get; }

		/// <summary>
		/// Получить все ошибки регистрации заявок.
		/// </summary>
		IEnumerable<OrderFail> OrderRegisterFails { get; }

		/// <summary>
		/// Получить все ошибки снятия заявок.
		/// </summary>
		IEnumerable<OrderFail> OrderCancelFails { get; }

		/// <summary>
		/// Получить все собственные сделки.
		/// </summary>
		IEnumerable<MyTrade> MyTrades { get; }

		/// <summary>
		/// Событие появления собственных новых сделок.
		/// </summary>
		event Action<IEnumerable<MyTrade>> NewMyTrades;

		/// <summary>
		/// Событие появления новой заявки.
		/// </summary>
		event Action<Order> NewOrder;

		/// <summary>
		/// Событие изменения состояния заявки (снята, удовлетворена).
		/// </summary>
		event Action<Order> OrderChanged;

		/// <summary>
		/// Событие об ошибке регистрации или отмены заявки.
		/// </summary>
		event Action<OrderFail> OrderFailed;

		/// <summary>
		/// Событие, сигнализирующее об ошибке при получении или обработке новых данных.
		/// </summary>
		event Action<Exception> ProcessDataError;

		/// <summary>
		/// Событие о новых сделках.
		/// </summary>
		event Action<IEnumerable<Trade>> NewTrades;

		/// <summary>
		/// Событие о изменении стакана.
		/// </summary>
		event Action<IEnumerable<MarketDepth>> MarketDepthsChanged;

		/// <summary>
		/// Событие появления новых записей в логе заявок.
		/// </summary>
		event Action<IEnumerable<OrderLogItem>> NewOrderLogItems;

		/// <summary>
		/// Событие о изменении времени эмулятора.
		/// </summary>
		event Action MarketTimeChanged;

		/// <summary>
		/// Оповещение об обработке сообщения. Для входящих сообщений вызывается при их чтении из очереди.
		/// Для исходящих сообщений вызывается при их постановке в очередь исходящих.
		/// </summary>
		event Action<Message> MessageSent;

		/// <summary>
		/// Оповещение о вызове всех событий соответствующих приходу сообщения на сторону клиента. 
		/// Это происходит в момент чтения из исходящей очереди.
		/// </summary>
		event Action<Message> MessageReceived;

		/// <summary>
		/// Зарегистрировать заявку в эмуляторе.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		void RegisterOrder(Order order);

		/// <summary>
		/// Отменить заявку в эмуляторе.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменить.</param>
		void CancelOrder(Order order);

		/// <summary>
		/// Перерегистрировать заявку в эмуляторе.
		/// </summary>
		/// <param name="oldOrder">Старая заявка.</param>
		/// <param name="newOrder">Новая заявка.</param>
		void ReRegisterOrder(Order oldOrder, Order newOrder);

		/// <summary>
		/// Обновить эмулятор новыми данными о стаканах.
		/// </summary>
		/// <param name="marketDepth">Новый стакан.</param>
		/// <param name="delta">Приращение от старого стакана к новому. Если значение null, прарещение будет вычислено автоматически.</param>
		void UpdateQuotes(MarketDepth marketDepth, MarketDepth delta);

		/// <summary>
		/// Обновить эмулятор новыми данными о сделках.
		/// </summary>
		/// <param name="newTrades">Новые сделки.</param>
		void UpdateTrades(IEnumerable<Trade> newTrades);

		/// <summary>
		/// Обновить эмулятор новыми данными об ордерлоге.
		/// </summary>
		/// <param name="newItems">Новые сделки.</param>
		void UpdateOrderLog(IEnumerable<OrderLogItem> newItems);

		/// <summary>
		/// Добавить сообщение в очередь и провести его эмуляцию (и более ранних сообщений).
		/// </summary>
		/// <param name="msg">Входное сообщение.</param>
		void Emulate(Message msg);

		/// <summary>
		/// Получить настройки для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Настройки для инструмента.</returns>
		SecurityEmulationSettings GetSettings(Security security);

		/// <summary>
		/// Текущее время в эмуляторе.
		/// </summary>
		DateTime CurrentTime { get; }

		/// <summary>
		/// Запущен ли эмулятор.
		/// </summary>
		bool IsStarted { get; }

		/// <summary>
		/// Сброосить состояние эмулятора и установить <see cref="CurrentTime"/>.
		/// </summary>
		/// <param name="time">Текущее время в эмуляторе.</param>
		void Reset(DateTime time);

		/// <summary>
		/// Запустить эмулятор.
		/// </summary>
		void Start();

		/// <summary>
		/// Остановить эмулятор.
		/// </summary>
		void Stop();
	}
}
